import abc
import json
import time
import traceback
from threading import Thread
from websocket import WebSocketConnectionClosedException, create_connection


class WsClientHandlerBase(metaclass=abc.ABCMeta):
  @abc.abstractmethod
  def on_open(self):
    raise NotImplementedError()

  @abc.abstractmethod
  def on_close(self):
    raise NotImplementedError()

  @abc.abstractmethod
  def on_sent_msg(self, update):
    raise NotImplementedError()

  @abc.abstractmethod
  def on_recv_msg(self, update):
    raise NotImplementedError()

  @abc.abstractmethod
  def on_error(self, e, data=None):
    raise NotImplementedError()


class WsClientPrinter(WsClientHandlerBase):
  def __init__(self, *, verbose=True):
    self.verbose = verbose
    self.error = None

  def on_open(self):
    if self.verbose:
      print("Socket Opened")

  def on_close(self):
    if self.verbose:
      print("Socket Closed")

  def on_sent_msg(self, update):
    if self.verbose:
      print("Sent:", update.timestamp, update.msg)

  def on_recv_msg(self, update):
    if self.verbose:
      print("Recv:", update.timestamp, update.msg)

  def on_error(self, e, data=None):
    self.error = e
    if self.verbose:
      print("Error: {}, data {}".format(e, data))


class WsClientForwarder(WsClientHandlerBase):
  def __init__(self, *, handler, verbose=True):
    assert isinstance(handler, WsClientHandlerBase) or handler is None
    self._handler = handler
    self.verbose = verbose
    self.error = None

  def on_open(self):
    if self.verbose:
      print("Socket Opened")
    self._handler.on_open()

  def on_close(self):
    if self.verbose:
      print("Socket Closed")
    self._handler.on_close()

  def on_sent_msg(self, update):
    if self.verbose:
      print("Sent:", update.timestamp, update.msg)
    self._handler.on_sent_msg(update)

  def on_recv_msg(self, update):
    if self.verbose:
      print("Recv:", update.timestamp, update.msg)
    self._handler.on_recv_msg(update)

  def on_error(self, e, data=None):
    self.error = e
    if self.verbose:
      print("Error: {}, data {}".format(e, data))
    self._handler.on_error(e, data=data)


def _verify_url(url):
  if url[-1] == "/":
    url = url[:-1]
  return url


class WebsocketUpdate(object):
  def __init__(self, timestamp, msg):
    self.timestamp = timestamp
    self.msg = msg

  def __str__(self):
    return "<WebsocketUpdate: %s, %s>" % (self.timestamp, self.msg)


class WebsocketRequestBase(object):
  __metaclass__ = abc.ABCMeta

  @staticmethod
  @abc.abstractmethod
  def FromSingleProduct(product):
    raise NotImplementedError()


class WebsocketClientBase(metaclass=abc.ABCMeta):
  def __init__(self, *, url, request, handler, verbose=False):
    self._url = _verify_url(url)
    assert isinstance(request, self.RequestType)
    self._request = request
    self._handler = handler
    self._verbose = verbose
    self._ws = None
    self._listening = False
    self._sent_msg_count = None
    self._recv_msg_count = None
    self._last_heartbeat = None

  def _open(self, url):
    assert self._ws is None
    self._ws = create_connection(url)

  def _close(self):
    assert self._ws is not None
    self._ws.close()
    self._ws = None

  def _ws_send(self, msg):
    timestamp = time.time()
    if self._verbose:
      print("send", timestamp, msg)
    self._ws.send(msg)
    return WebsocketUpdate(timestamp, msg)

  def _ws_ping(self, msg):
    timestamp = time.time()
    if self._verbose:
      print("ping", timestamp, msg)
    self._ws.ping(msg)
    return WebsocketUpdate(timestamp, msg)

  def _ws_recv(self):
    msg = self._ws.recv()
    timestamp = time.time()
    if self._verbose:
      print("recv", timestamp, msg)
    return WebsocketUpdate(timestamp, msg)

  def start(self):
    def go():
      self._connect()
      self._heartbeat()
      self._listen()
      self._disconnect()

    self._listening = True
    self._sent_msg_count = 0
    self._recv_msg_count = 0
    self._last_heartbeat = 0
    self._thread = Thread(target=go)
    self._thread.start()

  def stop(self):
    self._listening = False
    self._sent_msg_count = self._sent_msg_count  # done
    self._recv_msg_count = self._recv_msg_count  # done
    self._last_heartbeat = self._last_heartbeat  # done
    self._thread.join()

  def printInfo(self):
    print("info", self._url, self._sent_msg_count, self._recv_msg_count)

  def getSentMsgCount(self):
    return self._sent_msg_count

  def getRecvMsgCount(self):
    return self._recv_msg_count

  def getRequest(self):
    return self._request

  def set_feed_handler(self, handler):
    return self._handler.set_handler(handler)

  def _connect(self):
    try:
      self._open(self._url)
    except Exception as e:
      pass
    try:
      self._handler.on_open()
    except Exception as e:
      print("HandlerException::on_open", type(e), e)
    self._connect_impl()
    # TODO: wait

  def _disconnect(self):
    self._disconnect_impl()
    try:
      self._close()
    except WebSocketConnectionClosedException as e:
      pass
    try:
      self._handler.on_close()
    except Exception as e:
      print("HandlerException::on_close", type(e), e)

  def _send(self, msg):
    update = self._ws_send(msg)
    self._sent_msg_count = self._sent_msg_count + 1
    try:
      self._handler.on_sent_msg(update)
    except Exception as e:
      print("HandlerException::on_sent_msg", type(e), e)
    return update

  def _send_as_json(self, msg):
    update = self._ws_send(json.dumps(msg))
    self._sent_msg_count = self._sent_msg_count + 1
    try:
      self._handler.on_sent_msg(update)
    except Exception as e:
      print("HandlerException::on_sent_msg", type(e), e)
    return update

  def _recv(self):
    update = None
    try:
      update = self._ws_recv()
      self._recv_msg_count = self._recv_msg_count + 1
    except ValueError as e:
      self._handler.on_error(e)
    except Exception as e:
      self._handler.on_error(e)

    if update is not None:
      try:
        self._handler.on_recv_msg(update)
      except Exception as e:
        print("HandlerException::on_recv_msg", type(e), e, traceback.format_exc())
    return update

  def _listen(self):
    while self._listening:
      if (time.time() - self._last_heartbeat) >= 15:
        self._heartbeat()
      self._recv()

  def _heartbeat(self):
    self._heartbeat_impl()
    self._last_heartbeat = time.time()

  @abc.abstractmethod
  def _connect_impl(self):
    raise NotImplementedError()

  @abc.abstractmethod
  def _disconnect_impl(self):
    raise NotImplementedError()

  @abc.abstractmethod
  def _heartbeat_impl(self):
    raise NotImplementedError()
